package cc.rboot.tree.core;

import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.TableField;
import cc.rboot._core.util.ListUtils;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
 * 节点树
 * 1.建议使用实际的节点domain继承该类（不要直接使用该类）
 * 然后从数据库中读取到完整的节点列表后，使用new XXX().create 方法创建完整节点树，并缓存使用
 * 2.建议创建一个节点管理器来缓存节点，比如GoodsCategoryManager
 *
 * @param <T> id的类型
 */
public class TreeNode<T> implements Serializable {

    @TableField(exist = false)
    private T id;

    @TableField(exist = false)
    @JSONField(serialize = false)
    private TreeNode<T> parent;

    @TableField(exist = false)
    private List<TreeNode<T>> children = new ArrayList<>();

    /**
     * 根据list创建完整树，把当前节点作为虚拟根，并返回当前节点
     *
     * @param nodeList            节点列表
     * @param getIdFunction       获取节点id的方法
     * @param getParentIdFunction 获取节点父id的方法
     * @return 完整节点树
     */
    public <S extends TreeNode<T>> S create(List<S> nodeList, Function<S, T> getIdFunction, Function<S, T> getParentIdFunction) {
        Map<T, S> map = ListUtils.toMap(nodeList, getIdFunction);
        for (S node : nodeList) {
            node.setId(getIdFunction.apply(node));
            if (getParentIdFunction.apply(node) == null || getParentIdFunction.apply(node).equals(0)) {
                node.setParent(this.current());
            } else {
                node.setParent(map.get(getParentIdFunction.apply(node)));
            }
        }
        return this.current();
    }

    /**
     * 在当前节点后添加子节点
     *
     * @param childNode 子节点
     */
    public <S extends TreeNode<T>> S addNode(S childNode) {
        this.children.add(childNode);
        return this.current();
    }

    /**
     * 在指定父节点后添加子节点
     *
     * @param parentId  父节点id
     * @param childNode 要添加的子节点
     */
    public <S extends TreeNode<T>> S addNode(Integer parentId, S childNode) throws Exception {
        final S lookup = this.lookup(parentId);
        if (lookup == null) {
            throw new Exception("找不到父节点");
        }
        return lookup.addNode(childNode);
    }

    /**
     * 获取节点树的虚拟根
     *
     * @return 节点树的虚拟根
     */
    public <S extends TreeNode<T>> S virtualRoot() {
        S current = this.current();
        while (true) {
            if (current.getParent() == null) {
                return current;
            }
            current = current.getParent();
        }
    }

    /**
     * 在整个节点树上查找指定id的节点
     *
     * @param id 指定id
     * @return 指定id的节点
     */
    public <S extends TreeNode<T>> S lookup(Integer id) {
        S virtualRoot = virtualRoot();
        return lookupFrom(virtualRoot, id);
    }

    /**
     * 从指定节点开始查找指定id的节点
     *
     * @param node 指定节点
     * @param id   指定id
     * @return 指定id的节点
     */
    public <S extends TreeNode<T>> S lookupFrom(S node, Integer id) {
        if (id.equals(node.getId())) {
            return node;
        }
        List<S> children = node.getChildren();

        for (S child : children) {
            S lookupNode = lookupFrom(child, id);
            if (lookupNode != null) {
                return lookupNode;
            }
        }
        return null;
    }

    /**
     * 获取当前节点的所有叶子节点
     *
     * @return 所有叶子节点
     */
    public <S extends TreeNode<T>> List<S> leafList() {
        List<S> collector = new ArrayList<>();
        this.leafListCollector(this.current(), collector);
        return collector;
    }

    /**
     * 获取当前节点树上的所有节点
     *
     * @return 树中所有节点
     */
    public <S extends TreeNode<T>> List<S> toList() {
        List<S> collector = new ArrayList<>();
        this.treeToListCollect(this.current(), collector);
        return collector;
    }

    /**
     * 递归收集指定节点的叶子节点
     *
     * @param node      指定节点
     * @param collector 收集器
     */
    private <S extends TreeNode<T>> void leafListCollector(S node, List<S> collector) {
        if (node.getChildren().isEmpty()) {
            collector.add(node);
            return;
        }
        final List<S> children = node.getChildren();
        for (S child : children) {
            this.leafListCollector(child, collector);
        }
    }

    /**
     * 递归将树转为集合
     * @param node      指定节点
     * @param collector 收集器
     */
    private <S extends TreeNode<T>> void treeToListCollect(S node, List<S> collector) {
        if (node.getChildren().isEmpty()) {
            collector.add(node);
            return;
        }
        final List<S> children = node.getChildren();
        node.setChildren(null);
        collector.add(node);
        for (S child : children) {
            this.treeToListCollect(child, collector);
        }
    }

    private <S extends TreeNode<T>> S current() {
        return (S) this;
    }

    public T getId() {
        return id;
    }

    public void setId(T id) {
        this.id = id;
    }

    public <S extends TreeNode<T>> S getParent() {
        return (S) parent;
    }

    public <S extends TreeNode<T>> void setParent(S parent) {
        this.parent = parent;
        if (parent != null) {
            parent.addNode(this.current());
        }
    }

    public <S extends TreeNode<T>> List<S> getChildren() {
        return (List<S>) children;
    }

    public void setChildren(List<TreeNode<T>> children) {
        this.children = children;
    }

    @Override
    public String toString() {
        return "TreeNode{" +
                ", id=" + id +
                '}';
    }
}
